package cn.sample.security.provider;

import cn.sample.common.properties.SysProperties;
import cn.sample.system.bean.User;
import cn.sample.system.service.ResourceSupport;
import cn.sample.system.service.UserSupport;
import com.github.yitter.idgen.YitIdHelper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.util.Assert;

import javax.annotation.Resource;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * <h2></h2>
 *
 * <p>自定义用户名密码登录认证提供类</p>
 *
 * @author HKD
 */
@Slf4j
public class UsernamePasswordAuthenticationProvider implements AuthenticationProvider, InitializingBean, MessageSourceAware {

    // 用户服务类
    private final UserSupport userSupport;
    // 用户资源服务类
    private ResourceSupport resourceSupport;
    // 密码加密规则类
    private final PasswordEncoder passwordEncoder;
    // 国际化
    private MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();

    @Resource
    private SysProperties sysProperties;

    @Resource
    private RedisTemplate<String,Object> redisTemplate;

    public UsernamePasswordAuthenticationProvider(UserSupport userSupport, ResourceSupport resourceSupport, PasswordEncoder passwordEncoder) {
        this.userSupport = userSupport;
        this.resourceSupport = resourceSupport;
        this.passwordEncoder = passwordEncoder;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        Assert.notNull(userSupport, "userSupport 不能为空");
        Assert.notNull(passwordEncoder, "passwordEncoder 不能为空");
    }

    @Override
    public void setMessageSource(@NotNull MessageSource messageSource) {
        this.messages = new MessageSourceAccessor(messageSource);
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
                () -> this.messages.getMessage("UsernamePasswordAuthenticationProvider.onlySupports",
                        "认证异常！"));
        //未授信认证
        UsernamePasswordAuthenticationToken noAuthenticationToken = (UsernamePasswordAuthenticationToken) authentication;
        String username = noAuthenticationToken.getName();
        String password = (String) noAuthenticationToken.getCredentials();

        if(StringUtils.isBlank(username)){
            throw new UsernameNotFoundException("用户名或密码错误！");
        }

        // 根据用户名查询 user 对象
        User user = userSupport.loadUserByUsername(username);

        if (Objects.isNull(user)) {
            throw new UsernameNotFoundException("用户名或密码错误！");
        }

        // 密码校验
        if (this.passwordEncoder.matches(password, user.getPassword())) {
            return createSuccessAuthentication(authentication,user);
        }else{
            throw new BadCredentialsException(this.messages
                    .getMessage("UsernamePasswordAuthenticationProvider.badCredentials", "用户名或密码错误！"));
        }
    }

    /**
     * 创建认证成功的认证对象
     * @param authentication    原始认证对象
     * @param user  系统 User
     * @return 认证成功对象
     */
    protected Authentication createSuccessAuthentication(Authentication authentication, User user) {
        // 去除用户密码
        user.setPassword(null);

        // 获取用户权限
        String resCacheKey = StringUtils.join(sysProperties.getProject(),":system:res:",user.getUsername());

        List<cn.sample.system.bean.Resource> resourceList = resourceSupport.getResourceByUserId(user.getId());
        // 处理用户权限为 security 的权限格式
        String[] res = resourceList.stream().map(cn.sample.system.bean.Resource::getResource).toArray(String[]::new);

        // 权限存入缓存
        redisTemplate.opsForValue().set(resCacheKey,resourceList);

        // 生成认证 token
        String token = Long.toString(YitIdHelper.nextId());
        // token 存入缓存
        String tokenCacheKey = StringUtils.join(sysProperties.getProject(),":security:auto:",user.getUsername(),":",token);
        // 设置 token 半小时过期
        redisTemplate.opsForValue().set(tokenCacheKey,user,1800, TimeUnit.SECONDS);

        UsernamePasswordAuthenticationToken authenticated = UsernamePasswordAuthenticationToken.authenticated(token,null, AuthorityUtils.createAuthorityList(res));
        authenticated.setDetails(user);
        return authenticated;
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
    }


}
